package sweet.mvc.api

import sweet.helper.Logger
import sweet.helper.JDate
import scala.collection.mutable.HashMap  
import scala.collection.mutable.ListBuffer
import java.util.Date

/** A function to convert form string value to another type value. */
sealed case class ValueConverter(val converter: String=>Any)
case object StringConverter extends ValueConverter(v => v)
case object IntConverter extends ValueConverter(v => v.toInt)
case object DoubleConverter extends ValueConverter(v => v.toDouble)
case object BooleanConverter extends ValueConverter(v => v.toBoolean)
case object DateConverter extends ValueConverter(v => JDate.mkdate(v))
case object DatetimeConverter extends ValueConverter(v => JDate.mkdatetime(v))

/** A form field that define all information for a View to properly render it. */
sealed case class Field(val name: String, val label: String, 
  val converter: ValueConverter, val validator: Validator){
  
  def isRequired = validator.combinedValidators.find(_.isInstanceOf[VRequired]) != None
}

/** Same as Field, but use to hold multi-values map like radio and select*/
case class MultiValuesField(_name: String, _label: String, 
  _converter: ValueConverter, _validator: Validator, 
  val multiValues: scala.collection.Map[String, String]) extends 
  Field(_name, _label, _converter, _validator){

  def multiValuesKeys = multiValues.keys.toList    
}

/** A validator is used to validating Form field value that comes from req object.
 * We expect all value from Form are String type simply validate this value.
 */
trait Validator extends Logger {  
  private val _list = { val lb = new ListBuffer[Validator]; lb += this; lb }
  /** a validator list will always contains itself first. */
  def combinedValidators = _list.readOnly
  
  /** Add another validator to this list. */
  def ++(v: Validator): Validator = { _list ++= v.combinedValidators; this }
  
  /** Abstract method to let subclass provide validation codes. 
   * return true if continue next validator, false not to continue*/
  def validate(fieldValue: String, fieldName: String, formData: FormData): Boolean
}

/** Faile validation if fieldValue is not empty, else let it pass to next validator. */
case class VRequired extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    if(fieldValue.trim == ""){
      formData.addError(fieldName, "This field is required.")
      false
    }else{ true }
  }
}

/** Stop next validator if fieldValue is empty. */
case class VOptional extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    if(fieldValue.trim == "") false else true
  }
}
/** Validate fieldValue's length to must fit within min and max inclusively. */
case class VLen(min: Int, max: Int, error: String) extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    if(fieldValue.length >=min && fieldValue.length <=max) true
    else{ formData.addError(fieldName, error); false }
  }
}
/** Validate the fieldValue to be all digits and then within range. */
case class VInt(min: Int, max: Int, error: String) extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    try{
      val num = fieldValue.toInt
      if(num >=min && num <=max) true
      else{ formData.addError(fieldName, error); false }
    }catch{
      case _ => { formData.addError(fieldName, "Invalid integer number input."); false }
    }
  }
}
/** Validate the fieldValue to be decimal and then within range. */
case class VDouble(min: Double, max: Double, error: String) extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    try{
      val num = fieldValue.toDouble
      if(num >=min && num <=max) true
      else{ formData.addError(fieldName, error); false }
    }catch{
      case _ => { formData.addError(fieldName, "Invalid decimal number input."); false }
    }
  }
}

/** Validate the fieldValue to be date and then within range. */
case class VDate(format: String, min: JDate, max: JDate, error: String) extends Validator{
  import sweet.helper.JDate._  
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    try{
      val dtm = mkdateFrom(fieldValue, format).millis
      if(dtm >= min.millis && dtm <= max.millis) true
      else{ formData.addError(fieldName, error); false }
    }catch{
      case _ => { formData.addError(fieldName, "Invalid date input."); false }
    }
  }
}
case class VDateAfter(format: String, checkDt: JDate, error: String) extends Validator{
  import sweet.helper.JDate._  
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    try{
      val dtm = mkdateFrom(fieldValue, format).millis
      if(dtm >= checkDt.millis) true
      else{ formData.addError(fieldName, error); false }
    }catch{
      case _ => { formData.addError(fieldName, "Invalid date input."); false }
    }
  }
}
case class VDateBefore(format: String, checkDt: JDate, error: String) extends Validator{
  import sweet.helper.JDate._  
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    try{
      val dtm = mkdateFrom(fieldValue, format).millis
      if(dtm <= checkDt.millis) true
      else{ formData.addError(fieldName, error); false }
    }catch{
      case _ => { formData.addError(fieldName, "Invalid date input."); false }
    }
  }
}
/** Validate the fieldValue by a custom function. */
case class VValue(error: String)(func: (String)=>Boolean) extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    if(func(fieldValue)) true
    else{ formData.addError(fieldName, error); false }
  }
}

/** Validate a email address. You should run VLen to prevent extreme long input, as the 
 * regex will not handle Large String well. */
case class VEmail(error: String) extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = {
    if(fieldValue.matches("""^[A-Za-z0-9\\.\-_]+@[A-Za-z0-9\\.\-_]+$""")) true
    else{ formData.addError(fieldName, error); false }
  }
}

/** Validate fieldValue, fieldValue and formData by a custom function. 
 * Note that since custom function has the formData, it will have full control
 * on addy any error msg it wants. */
case class VFunc(func: (String, String, FormData)=>Boolean) extends Validator{
  def validate(fieldValue: String, fieldName: String, formData: FormData) = 
    if(func(fieldValue, fieldName, formData)) true
    else false
}

/** A FormData instance is created per each request. 
 * While the Form fields definition maybe one per controller, but the values it process
 * is per request. The Form holds the Field definition that might be constant, while
 * FormData capture the form's actual input per request. This class will initialy
 * contains a empty FormErrors instances, and during validation, any validator are
 * free to add error msg into it. 
 *
 * All values are store in List of String that can support multi values inputs. For
 * single values, use #singleValues to retrieve it.
 */
class FormData(val form: Form) extends Logger{
  import org.apache.commons.lang.StringEscapeUtils
  
  val formName = form.formName
  val fields = form.fields
  
  private val _values = new HashMap[String, ListBuffer[String]]
    
  /** Inserting more values into this instance. */
  def addValue(fieldName: String, fieldValue: String){ 
    _values.getOrElseUpdate(fieldName, new ListBuffer[String]) += fieldValue
  }
  def addEmptyValue(fieldName: String){
    _values.getOrElseUpdate(fieldName, new ListBuffer[String])    
  }
  
  /** Retrive raw string multi values map*/
  def rawMultiValues: Map[String, List[String]] = Map() ++ (for((k,v) <- _values) yield (k, v.toList))
  
  /** Retrive raw string single value map.*/
  def rawSingleValues: Map[String, String] = {
    val singles = _values.filter{ entry=> (entry._2.size == 1) }
    Map() ++ (for((k,v) <- singles) yield (k, v(0)))  
  }
  
  def rawSingleValue(fieldName: String): String = _values(fieldName)(0)
  
  /** Calls to #rawSingleValue */
  def apply(fieldName: String) = rawSingleValue(fieldName)  
    
  
  /** Retrieve html escaped multi values map. */
  def multiValues: Map[String, List[String]] =
    Map() ++ (for((k,v) <- _values) yield (k, v.map{StringEscapeUtils.escapeHtml(_)}.toList))
      
  /** Retrieve html escaped multi values for a given fieldName. */
  def multiValues(fieldName: String): List[String] = 
    _values(fieldName).map{ StringEscapeUtils.escapeHtml(_) }.toList
  
    /** Retrieve html escaped single value for a fieldName. */
  def singleValues: Map[String, String] = {
    val singles = _values.filter{ entry=> (entry._2.size == 1) }
    Map() ++ (for((k,v) <- singles) yield (k, StringEscapeUtils.escapeHtml(v(0))))
  }
    
  /** Retrieve html escaped single value for a given fieldName. */
  def singleValue(fieldName: String): String = 
    StringEscapeUtils.escapeHtml(_values(fieldName)(0))
 
  /** implementation of Map.size */
  def size = _values.size
  
  private val _errors = new HashMap[String, ListBuffer[String]]
  def errors: Map[String, List[String]] = Map() ++ _errors.map{ case (k,v) => (k, v.map(StringEscapeUtils.escapeHtml(_)).toList) }
  def addError(fieldName: String, errorMsg: String) =
    _errors.getOrElseUpdate(fieldName, new ListBuffer[String]) += errorMsg
  def hasError = _errors.size > 0 
}

/** A form object is created to represent the HTML form view. You add all the Field
 * that match to your input form, and also provide label, validation, and value convertor
 * if any to each field. You only need a single instance of form object per HTML form view,
 * or controller. You would call #initialFormData for request that ask for form view,
 * and then call #processFormData for request that process the form.
 * 
 * In order to have form binding(extracting parameters from request that matching your
 * form field) you must create your HTML Form field name with a formName as prefix then a dot.
 * This prefix will be use to extract form values from request parameters.
 */
class Form(val formName: String) extends Logger {
  val prefix = formName+"."
  val prefixLen = prefix.length
       
  //field container
  private val _fields = new HashMap[String, Field]
  def fields = _fields.readOnly
  def fieldNames = _fields.keys.toList
    
  def fieldNameLookup(fieldName: String) = prefix+fieldName
  
  /** Return instance of FormData for initial view of the form input.
   * Default is provided with each value a empty string. */
  def initialFormData(req: Req): FormData = {
    val formData = new FormData(this)
    for((name, field) <- _fields) field match {
      case MultiValuesField(_,_,_,_,map) => 
        for(x <- map.keys)
          formData.addValue(name, initialFieldValue(x, field, formData, req))
      case _ => formData.addValue(name, initialFieldValue(field, formData, req))
    }
    formData
  }
  
  def initialFieldValue(multiValKey: String, field: Field, formData: FormData, req: Req): String = initialFieldValue(multiValKey, field)
  def initialFieldValue(multiValKey: String, field: Field): String = ""
  
  def initialFieldValue(field: Field, formData: FormData, req: Req): String = initialFieldValue(field)
  def initialFieldValue(field: Field): String = ""
      
  /** Return instance of  per processing request. This method will
   * validate all fields and extract fieldValue(s) into the  instance.*/
  def validateFormData(req: Req): FormData = {  
    val formData = new FormData(this)
    
    //For each field, extract it from request parameters.
    logger.debug("Extracting formData from request parameters.")
    for((name,field)<-_fields){
      if(field.isInstanceOf[MultiValuesField]){
          req.params(fieldNameLookup(name)) match {
            case Some(aryVal)=> for(x <- aryVal) formData.addValue(name, x)
            case None => formData.addEmptyValue(name)
          }        
      }else{
        formData.addValue(name, req(fieldNameLookup(name)))
      }
    }
    
    //For each field, validate the values.
    logger.debug("Validating formData.")
    for((fieldName, field) <- _fields){
      val fieldValues = formData.multiValues(fieldName)
      val fieldValue = if(fieldValues.size>0) fieldValues(0) else ""      
      val validators = field.validator.combinedValidators
      
      logger.debug("Validating "+fieldName+": "+fieldValue+" with "+validators.size+" validators")
      var continue = true
      var velem = validators.elements
      while(continue && velem.hasNext){
        val validator = velem.next
        logger.debug("Running validator "+validator)
        continue = validator.validate(fieldValue, fieldName, formData)
        logger.debug("Done. Continue to next validator? "+continue)
      }      
    }
    formData
  }   
  
  def addField(fieldName: String, fieldLabel: String, converter: ValueConverter)(validator: Validator){
    _fields(fieldName) = new Field(fieldName, fieldLabel, converter, validator)
  }
  def addMultiValuesField(fieldName: String, fieldLabel: String, 
    converter: ValueConverter, validator: Validator)(multiValues: scala.collection.Map[String, String]){
    _fields(fieldName) = new MultiValuesField(fieldName, fieldLabel, converter, validator, multiValues)
  }
}

/** A trait that define pair of handlers for get form and process form */
trait FormHandler{
  def viewForm(formData: FormData, req: Req): View
  def processForm(formData: FormData, req: Req): View
}

trait FormController extends Controller {
    
  /* If post then it's a form submit. Subclass may override to look for request
   * values to determine if needed */
  def isFormSubmit(req: Req) = req.method match {
    case Get => false
    case Post => true
  }
  
  /** Add default action to handle form processing. */
  def handleForm(url: String, form: Form)(formHandler: FormHandler){
    _handlers(url) = (req: Req, resp: Resp) => {
      if(isFormSubmit(req)){
        logger.debug("Processing form.")
        val frm = form     
        val formData = frm.validateFormData(req)
        
        if(formData.hasError){
          logger.warn("Form validation failed with errors. Return form view.")
          View(url, Map("formData"->formData))
        }else{ 
          logger.info("Form validation passed.")
          val view = formHandler.processForm(formData, req)
          View(view, Map("formData"->formData))
        }
      }else{        
        logger.debug("Getting form view.")
        val frm = form     
        val formData = frm.initialFormData(req)
        val view = formHandler.viewForm(formData, req)
        View(view, Map("formData"->formData))
      }  
    }
  }
}
